/**
 * $RCSfile$
 * $Revision: 11825 $
 * $Date: 2010-08-15 23:45:57 +0800 (周日, 15 八月 2010) $
 *
 * Copyright 2003-2007 Jive Software.
 *
 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.smack.util;

import org.jivesoftware.smack.Connection;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Utility class that helps to parse packets. Any parsing packets method that
 * must be shared between many clients must be placed in this utility class.
 * 
 * @author Gaston Dombiak
 */
public class PacketParserUtils
{

	/**
	 * Namespace used to store packet properties.
	 */
	private static final String PROPERTIES_NAMESPACE = "http://www.jivesoftware.com/xmlns/xmpp/properties";

	/**
	 * Parses a message packet.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of a message packet.
	 * @return a Message packet.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static Packet parseMessage(XmlPullParser parser) throws Exception
	{
		Message message = new Message();
		String id = parser.getAttributeValue("", "id");
		message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
		message.setTo(parser.getAttributeValue("", "to"));
		message.setFrom(parser.getAttributeValue("", "from"));
		message.setType(Message.Type.fromString(parser.getAttributeValue("",
		        "type")));
		String language = getLanguageAttribute(parser);

		// determine message's default language
		String defaultLanguage = null;
		if (language != null && !"".equals(language.trim()))
		{
			message.setLanguage(language);
			defaultLanguage = language;
		}
		else
		{
			defaultLanguage = Packet.getDefaultLanguage();
		}

		// Parse sub-elements. We include extra logic to make sure the values
		// are only read once. This is because it's possible for the names to
		// appear
		// in arbitrary sub-elements.
		boolean done = false;
		String thread = null;
		Map<String, Object> properties = null;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				String elementName = parser.getName();
				String namespace = parser.getNamespace();
				if (elementName.equals("subject"))
				{
					String xmlLang = getLanguageAttribute(parser);
					if (xmlLang == null)
					{
						xmlLang = defaultLanguage;
					}

					String subject = parseContent(parser);

					if (message.getSubject(xmlLang) == null)
					{
						message.addSubject(xmlLang, subject);
					}
				}
				else if (elementName.equals("body"))
				{
					String xmlLang = getLanguageAttribute(parser);
					if (xmlLang == null)
					{
						xmlLang = defaultLanguage;
					}

					String body = parseContent(parser);

					if (message.getBody(xmlLang) == null)
					{
						message.addBody(xmlLang, body);
					}
				}
				else if (elementName.equals("thread"))
				{
					if (thread == null)
					{
						thread = parser.nextText();
					}
				}
				else if (elementName.equals("error"))
				{
					message.setError(parseError(parser));
				}
				else if (elementName.equals("properties")
				        && namespace.equals(PROPERTIES_NAMESPACE))
				{
					properties = parseProperties(parser);
				}
				// Otherwise, it must be a packet extension.
				else
				{
					message.addExtension(PacketParserUtils
					        .parsePacketExtension(elementName, namespace,
					                parser));
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("message"))
				{
					done = true;
				}
			}
		}

		message.setThread(thread);
		// Set packet properties.
		if (properties != null)
		{
			for (String name : properties.keySet())
			{
				message.setProperty(name, properties.get(name));
			}
		}
		return message;
	}

	/**
	 * Returns the content of a tag as string regardless of any tags included.
	 * 
	 * @param parser
	 *            the XML pull parser
	 * @return the content of a tag as string
	 * @throws XmlPullParserException
	 *             if parser encounters invalid XML
	 * @throws IOException
	 *             if an IO error occurs
	 */
	private static String parseContent(XmlPullParser parser)
	        throws XmlPullParserException, IOException
	{
		String content = "";
		int parserDepth = parser.getDepth();
		while (!(parser.next() == XmlPullParser.END_TAG && parser.getDepth() == parserDepth))
		{
			content += parser.getText();
		}
		return content;
	}

	/**
	 * Parses a presence packet.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of a presence packet.
	 * @return a Presence packet.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static Presence parsePresence(XmlPullParser parser) throws Exception
	{
		Presence.Type type = Presence.Type.available;
		String typeString = parser.getAttributeValue("", "type");
		if (typeString != null && !typeString.equals(""))
		{
			try
			{
				type = Presence.Type.valueOf(typeString);
			}
			catch (IllegalArgumentException iae)
			{
				System.err.println("Found invalid presence type " + typeString);
			}
		}
		Presence presence = new Presence(type);
		presence.setTo(parser.getAttributeValue("", "to"));
		presence.setFrom(parser.getAttributeValue("", "from"));
		String id = parser.getAttributeValue("", "id");
		presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);

		String language = getLanguageAttribute(parser);
		if (language != null && !"".equals(language.trim()))
		{
			presence.setLanguage(language);
		}
		presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);

		// Parse sub-elements
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				String elementName = parser.getName();
				String namespace = parser.getNamespace();
				if (elementName.equals("status"))
				{
					presence.setStatus(parser.nextText());
				}
				else if (elementName.equals("priority"))
				{
					try
					{
						int priority = Integer.parseInt(parser.nextText());
						presence.setPriority(priority);
					}
					catch (NumberFormatException nfe)
					{
						// Ignore.
					}
					catch (IllegalArgumentException iae)
					{
						// Presence priority is out of range so assume priority
						// to be zero
						presence.setPriority(0);
					}
				}
				else if (elementName.equals("show"))
				{
					String modeText = parser.nextText();
					try
					{
						presence.setMode(Presence.Mode.valueOf(modeText));
					}
					catch (IllegalArgumentException iae)
					{
						System.err.println("Found invalid presence mode "
						        + modeText);
					}
				}
				else if (elementName.equals("error"))
				{
					presence.setError(parseError(parser));
				}
				else if (elementName.equals("properties")
				        && namespace.equals(PROPERTIES_NAMESPACE))
				{
					Map<String, Object> properties = parseProperties(parser);
					// Set packet properties.
					for (String name : properties.keySet())
					{
						presence.setProperty(name, properties.get(name));
					}
				}
				// Otherwise, it must be a packet extension.
				else
				{
					presence.addExtension(PacketParserUtils
					        .parsePacketExtension(elementName, namespace,
					                parser));
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("presence"))
				{
					done = true;
				}
			}
		}
		return presence;
	}

	/**
	 * Parses an IQ packet.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of an IQ packet.
	 * @return an IQ object.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static IQ parseIQ(XmlPullParser parser, Connection connection)
	        throws Exception
	{
		IQ iqPacket = null;

		String id = parser.getAttributeValue("", "id");
		String to = parser.getAttributeValue("", "to");
		String from = parser.getAttributeValue("", "from");
		IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type"));
		XMPPError error = null;

		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG)
			{
				String elementName = parser.getName();
				String namespace = parser.getNamespace();
				if (elementName.equals("error"))
				{
					error = PacketParserUtils.parseError(parser);
				}
				else if (elementName.equals("query")
				        && namespace.equals("jabber:iq:auth"))
				{
					iqPacket = parseAuthentication(parser);
				}
				else if (elementName.equals("query")
				        && namespace.equals("jabber:iq:roster"))
				{
					iqPacket = parseRoster(parser);
				}
				else if (elementName.equals("query")
				        && namespace.equals("jabber:iq:register"))
				{
					iqPacket = parseRegistration(parser);
				}
				else if (elementName.equals("bind")
				        && namespace.equals("urn:ietf:params:xml:ns:xmpp-bind"))
				{
					iqPacket = parseResourceBinding(parser);
				}
				// Otherwise, see if there is a registered provider for
				// this element name and namespace.
				else
				{
					Object provider = ProviderManager.getInstance()
					        .getIQProvider(elementName, namespace);
					if (provider != null)
					{
						if (provider instanceof IQProvider)
						{
							iqPacket = ((IQProvider) provider).parseIQ(parser);
						}
						else if (provider instanceof Class)
						{
							iqPacket = (IQ) PacketParserUtils
							        .parseWithIntrospection(elementName,
							                (Class) provider, parser);
						}
					}
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("iq"))
				{
					done = true;
				}
			}
		}
		// Decide what to do when an IQ packet was not understood
		if (iqPacket == null)
		{
			if (IQ.Type.GET == type || IQ.Type.SET == type)
			{
				// If the IQ stanza is of type "get" or "set" containing a child
				// element
				// qualified by a namespace it does not understand, then answer
				// an IQ of
				// type "error" with code 501 ("feature-not-implemented")
				iqPacket = new IQ()
				{
					public String getChildElementXML()
					{
						return null;
					}
				};
				iqPacket.setPacketID(id);
				iqPacket.setTo(from);
				iqPacket.setFrom(to);
				iqPacket.setType(IQ.Type.ERROR);
				iqPacket.setError(new XMPPError(
				        XMPPError.Condition.feature_not_implemented));
				connection.sendPacket(iqPacket);
				return null;
			}
			else
			{
				// If an IQ packet wasn't created above, create an empty IQ
				// packet.
				iqPacket = new IQ()
				{
					public String getChildElementXML()
					{
						return null;
					}
				};
			}
		}

		// Set basic values on the iq packet.
		iqPacket.setPacketID(id);
		iqPacket.setTo(to);
		iqPacket.setFrom(from);
		iqPacket.setType(type);
		iqPacket.setError(error);

		return iqPacket;
	}

	private static Authentication parseAuthentication(XmlPullParser parser)
	        throws Exception
	{
		Authentication authentication = new Authentication();
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				if (parser.getName().equals("username"))
				{
					authentication.setUsername(parser.nextText());
				}
				else if (parser.getName().equals("password"))
				{
					authentication.setPassword(parser.nextText());
				}
				else if (parser.getName().equals("digest"))
				{
					authentication.setDigest(parser.nextText());
				}
				else if (parser.getName().equals("resource"))
				{
					authentication.setResource(parser.nextText());
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("query"))
				{
					done = true;
				}
			}
		}
		return authentication;
	}

	private static RosterPacket parseRoster(XmlPullParser parser)
	        throws Exception
	{
		RosterPacket roster = new RosterPacket();
		boolean done = false;
		RosterPacket.Item item = null;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				if (parser.getName().equals("item"))
				{
					String jid = parser.getAttributeValue("", "jid");
					String name = parser.getAttributeValue("", "name");
					// Create packet.
					item = new RosterPacket.Item(jid, name);
					// Set status.
					String ask = parser.getAttributeValue("", "ask");
					RosterPacket.ItemStatus status = RosterPacket.ItemStatus
					        .fromString(ask);
					item.setItemStatus(status);
					// Set type.
					String subscription = parser.getAttributeValue("",
					        "subscription");
					RosterPacket.ItemType type = RosterPacket.ItemType
					        .valueOf(subscription != null ? subscription
					                : "none");
					item.setItemType(type);
				}
				if (parser.getName().equals("group") && item != null)
				{
					final String groupName = parser.nextText();
					if (groupName != null && groupName.trim().length() > 0)
					{
						item.addGroupName(groupName);
					}
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("item"))
				{
					roster.addRosterItem(item);
				}
				if (parser.getName().equals("query"))
				{
					done = true;
				}
			}
		}
		return roster;
	}

	private static Registration parseRegistration(XmlPullParser parser)
	        throws Exception
	{
		Registration registration = new Registration();
		Map<String, String> fields = null;
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				// Any element that's in the jabber:iq:register namespace,
				// attempt to parse it if it's in the form <name>value</name>.
				if (parser.getNamespace().equals("jabber:iq:register"))
				{
					String name = parser.getName();
					String value = "";
					if (fields == null)
					{
						fields = new HashMap<String, String>();
					}

					if (parser.next() == XmlPullParser.TEXT)
					{
						value = parser.getText();
					}
					// Ignore instructions, but anything else should be added to
					// the map.
					if (!name.equals("instructions"))
					{
						fields.put(name, value);
					}
					else
					{
						registration.setInstructions(value);
					}
				}
				// Otherwise, it must be a packet extension.
				else
				{
					registration.addExtension(PacketParserUtils
					        .parsePacketExtension(parser.getName(),
					                parser.getNamespace(), parser));
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("query"))
				{
					done = true;
				}
			}
		}
		registration.setAttributes(fields);
		return registration;
	}

	private static Bind parseResourceBinding(XmlPullParser parser)
	        throws IOException, XmlPullParserException
	{
		Bind bind = new Bind();
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				if (parser.getName().equals("resource"))
				{
					bind.setResource(parser.nextText());
				}
				else if (parser.getName().equals("jid"))
				{
					bind.setJid(parser.nextText());
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("bind"))
				{
					done = true;
				}
			}
		}

		return bind;
	}

	/**
	 * Parse the available SASL mechanisms reported from the server.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of the mechanisms
	 *            stanza.
	 * @return a collection of Stings with the mechanisms included in the
	 *         mechanisms stanza.
	 * @throws Exception
	 *             if an exception occurs while parsing the stanza.
	 */
	public static Collection<String> parseMechanisms(XmlPullParser parser)
	        throws Exception
	{
		List<String> mechanisms = new ArrayList<String>();
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG)
			{
				String elementName = parser.getName();
				if (elementName.equals("mechanism"))
				{
					mechanisms.add(parser.nextText());
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("mechanisms"))
				{
					done = true;
				}
			}
		}
		return mechanisms;
	}

	/**
	 * Parse the available compression methods reported from the server.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of the compression
	 *            stanza.
	 * @return a collection of Stings with the methods included in the
	 *         compression stanza.
	 * @throws Exception
	 *             if an exception occurs while parsing the stanza.
	 */
	public static Collection<String> parseCompressionMethods(
	        XmlPullParser parser) throws IOException, XmlPullParserException
	{
		List<String> methods = new ArrayList<String>();
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG)
			{
				String elementName = parser.getName();
				if (elementName.equals("method"))
				{
					methods.add(parser.nextText());
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("compression"))
				{
					done = true;
				}
			}
		}
		return methods;
	}

	/**
	 * Parse a properties sub-packet. If any errors occur while de-serializing
	 * Java object properties, an exception will be printed and not thrown since
	 * a thrown exception will shut down the entire connection.
	 * ClassCastExceptions will occur when both the sender and receiver of the
	 * packet don't have identical versions of the same class.
	 * 
	 * @param parser
	 *            the XML parser, positioned at the start of a properties
	 *            sub-packet.
	 * @return a map of the properties.
	 * @throws Exception
	 *             if an error occurs while parsing the properties.
	 */
	public static Map<String, Object> parseProperties(XmlPullParser parser)
	        throws Exception
	{
		Map<String, Object> properties = new HashMap<String, Object>();
		while (true)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG
			        && parser.getName().equals("property"))
			{
				// Parse a property
				boolean done = false;
				String name = null;
				String type = null;
				String valueText = null;
				Object value = null;
				while (!done)
				{
					eventType = parser.next();
					if (eventType == XmlPullParser.START_TAG)
					{
						String elementName = parser.getName();
						if (elementName.equals("name"))
						{
							name = parser.nextText();
						}
						else if (elementName.equals("value"))
						{
							type = parser.getAttributeValue("", "type");
							valueText = parser.nextText();
						}
					}
					else if (eventType == XmlPullParser.END_TAG)
					{
						if (parser.getName().equals("property"))
						{
							if ("integer".equals(type))
							{
								value = Integer.valueOf(valueText);
							}
							else if ("long".equals(type))
							{
								value = Long.valueOf(valueText);
							}
							else if ("float".equals(type))
							{
								value = Float.valueOf(valueText);
							}
							else if ("double".equals(type))
							{
								value = Double.valueOf(valueText);
							}
							else if ("boolean".equals(type))
							{
								value = Boolean.valueOf(valueText);
							}
							else if ("string".equals(type))
							{
								value = valueText;
							}
							else if ("java-object".equals(type))
							{
								try
								{
									byte[] bytes = StringUtils
									        .decodeBase64(valueText);
									ObjectInputStream in = new ObjectInputStream(
									        new ByteArrayInputStream(bytes));
									value = in.readObject();
								}
								catch (Exception e)
								{
									e.printStackTrace();
								}
							}
							if (name != null && value != null)
							{
								properties.put(name, value);
							}
							done = true;
						}
					}
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("properties"))
				{
					break;
				}
			}
		}
		return properties;
	}

	/**
	 * Parses SASL authentication error packets.
	 * 
	 * @param parser
	 *            the XML parser.
	 * @return a SASL Failure packet.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static Failure parseSASLFailure(XmlPullParser parser)
	        throws Exception
	{
		String condition = null;
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG)
			{
				if (!parser.getName().equals("failure"))
				{
					condition = parser.getName();
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("failure"))
				{
					done = true;
				}
			}
		}
		return new Failure(condition);
	}

	/**
	 * Parses stream error packets.
	 * 
	 * @param parser
	 *            the XML parser.
	 * @return an stream error packet.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static StreamError parseStreamError(XmlPullParser parser)
	        throws IOException, XmlPullParserException
	{
		StreamError streamError = null;
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();

			if (eventType == XmlPullParser.START_TAG)
			{
				streamError = new StreamError(parser.getName());
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("error"))
				{
					done = true;
				}
			}
		}
		return streamError;
	}

	/**
	 * Parses error sub-packets.
	 * 
	 * @param parser
	 *            the XML parser.
	 * @return an error sub-packet.
	 * @throws Exception
	 *             if an exception occurs while parsing the packet.
	 */
	public static XMPPError parseError(XmlPullParser parser) throws Exception
	{
		final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas";
		String errorCode = "-1";
		String type = null;
		String message = null;
		String condition = null;
		List<PacketExtension> extensions = new ArrayList<PacketExtension>();

		// Parse the error header
		for (int i = 0; i < parser.getAttributeCount(); i++)
		{
			if (parser.getAttributeName(i).equals("code"))
			{
				errorCode = parser.getAttributeValue("", "code");
			}
			if (parser.getAttributeName(i).equals("type"))
			{
				type = parser.getAttributeValue("", "type");
			}
		}
		boolean done = false;
		// Parse the text and condition tags
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				if (parser.getName().equals("text"))
				{
					message = parser.nextText();
				}
				else
				{
					// Condition tag, it can be xmpp error or an application
					// defined error.
					String elementName = parser.getName();
					String namespace = parser.getNamespace();
					if (errorNamespace.equals(namespace))
					{
						condition = elementName;
					}
					else
					{
						extensions.add(parsePacketExtension(elementName,
						        namespace, parser));
					}
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals("error"))
				{
					done = true;
				}
			}
		}
		// Parse the error type.
		XMPPError.Type errorType = XMPPError.Type.CANCEL;
		try
		{
			if (type != null)
			{
				errorType = XMPPError.Type.valueOf(type.toUpperCase());
			}
		}
		catch (IllegalArgumentException iae)
		{
			// Print stack trace. We shouldn't be getting an illegal error type.
			iae.printStackTrace();
		}
		return new XMPPError(Integer.parseInt(errorCode), errorType, condition,
		        message, extensions);
	}

	/**
	 * Parses a packet extension sub-packet.
	 * 
	 * @param elementName
	 *            the XML element name of the packet extension.
	 * @param namespace
	 *            the XML namespace of the packet extension.
	 * @param parser
	 *            the XML parser, positioned at the starting element of the
	 *            extension.
	 * @return a PacketExtension.
	 * @throws Exception
	 *             if a parsing error occurs.
	 */
	public static PacketExtension parsePacketExtension(String elementName,
	        String namespace, XmlPullParser parser) throws Exception
	{
		// See if a provider is registered to handle the extension.
		Object provider = ProviderManager.getInstance().getExtensionProvider(
		        elementName, namespace);
		if (provider != null)
		{
			if (provider instanceof PacketExtensionProvider)
			{
				return ((PacketExtensionProvider) provider)
				        .parseExtension(parser);
			}
			else if (provider instanceof Class)
			{
				return (PacketExtension) parseWithIntrospection(elementName,
				        (Class) provider, parser);
			}
		}
		// No providers registered, so use a default extension.
		DefaultPacketExtension extension = new DefaultPacketExtension(
		        elementName, namespace);
		boolean done = false;
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				String name = parser.getName();
				// If an empty element, set the value with the empty string.
				if (parser.isEmptyElementTag())
				{
					extension.setValue(name, "");
				}
				// Otherwise, get the the element text.
				else
				{
					eventType = parser.next();
					if (eventType == XmlPullParser.TEXT)
					{
						String value = parser.getText();
						extension.setValue(name, value);
					}
				}
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals(elementName))
				{
					done = true;
				}
			}
		}
		return extension;
	}

	private static String getLanguageAttribute(XmlPullParser parser)
	{
		for (int i = 0; i < parser.getAttributeCount(); i++)
		{
			String attributeName = parser.getAttributeName(i);
			if ("xml:lang".equals(attributeName)
			        || ("lang".equals(attributeName) && "xml".equals(parser
			                .getAttributePrefix(i))))
			{
				return parser.getAttributeValue(i);
			}
		}
		return null;
	}

	public static Object parseWithIntrospection(String elementName,
	        Class objectClass, XmlPullParser parser) throws Exception
	{
		boolean done = false;
		Object object = objectClass.newInstance();
		while (!done)
		{
			int eventType = parser.next();
			if (eventType == XmlPullParser.START_TAG)
			{
				String name = parser.getName();
				String stringValue = parser.nextText();
				// PropertyDescriptor descriptor = new PropertyDescriptor(name,
				// objectClass);
				// Load the class type of the property.
				// Class propertyType = descriptor.getPropertyType();
				Class propertyType = object
				        .getClass()
				        .getClass()
				        .getMethod(
				                "get" + Character.toUpperCase(name.charAt(0))
				                        + name.substring(1)).getReturnType();
				// Get the value of the property by converting it from a
				// String to the correct object type.
				Object value = decode(propertyType, stringValue);
				// Set the value of the bean.
				// descriptor.getWriteMethod().invoke(object, value);
				object.getClass()
				        .getMethod(
				                "set" + Character.toUpperCase(name.charAt(0))
				                        + name.substring(1), propertyType)
				        .invoke(object, value);
			}
			else if (eventType == XmlPullParser.END_TAG)
			{
				if (parser.getName().equals(elementName))
				{
					done = true;
				}
			}
		}
		return object;
	}

	/**
	 * Decodes a String into an object of the specified type. If the object type
	 * is not supported, null will be returned.
	 * 
	 * @param type
	 *            the type of the property.
	 * @param value
	 *            the encode String value to decode.
	 * @return the String value decoded into the specified type.
	 * @throws Exception
	 *             If decoding failed due to an error.
	 */
	private static Object decode(Class type, String value) throws Exception
	{
		if (type.getName().equals("java.lang.String"))
		{
			return value;
		}
		if (type.getName().equals("boolean"))
		{
			return Boolean.valueOf(value);
		}
		if (type.getName().equals("int"))
		{
			return Integer.valueOf(value);
		}
		if (type.getName().equals("long"))
		{
			return Long.valueOf(value);
		}
		if (type.getName().equals("float"))
		{
			return Float.valueOf(value);
		}
		if (type.getName().equals("double"))
		{
			return Double.valueOf(value);
		}
		if (type.getName().equals("java.lang.Class"))
		{
			return Class.forName(value);
		}
		return null;
	}
}
